package pl.edu.agh.agents.agent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import pl.edu.agh.agents.exception.PathNotFoundException;
import pl.edu.agh.agents.gui.GUI;
import pl.edu.agh.agents.gui.GraphLoader;
import pl.edu.agh.agents.path.Path;
import pl.edu.agh.agents.state.State;
import pl.edu.agh.agents.state.StateSpace;

public class SystemManager {
	private final static SystemManager managerInstance = new SystemManager();
	private GUI gui;
	private List<State> states;
	private List<Agent> agents;
	private HashMap<Agent, Integer> agentBestPathSize;
	private HashMap<Agent, Path> agentBestPath;	
	private HashMap<Agent, Path> agentPath;			// path with agent every move
	private HashMap<Agent, Integer> agentPathSize;
	private int iterations = 0;
	private HashMap<Agent, Boolean> finished;
	private boolean blocked = false;

	private int currentAgentID;

	private SystemManager() {
		loadAgents(GUI.emptyGraphPath);
	}

	public static SystemManager getInstance() {
		return managerInstance;
	}

	public void setGUI(GUI gui){
		this.gui = gui;
	}

	public void loadAgents(String filename) {
		agents = new ArrayList<Agent>();
		finished = new HashMap<Agent, Boolean>();
		states = StateSpace.getInstance().getStates();

		GraphLoader.getInstance().loadGraphFromFile(gui, states, agents, filename);

		agentBestPathSize = new HashMap<Agent, Integer>();
		agentBestPath = new HashMap<Agent, Path>();
		agentPath = new HashMap<Agent, Path>();
		agentPathSize = new HashMap<Agent, Integer>();

		iterations = 0;
		currentAgentID = -1;
		calculateBestPaths();
	}

	public List<Agent> getAgents() {
		return agents;
	}

	public void addAgent(Agent agent) {
		agents.add(agent);
	}

	public void removeAgent(Agent agent) {
		agents.remove(agent);
	}

	public void makeIterationForAll() {
		if (!checkConfiguration() || isFinished()) {
			return;
		}
		for (int i = 0; i < agents.size() - finished.size(); i++) {
			if(!makeIterationForOne()){
				return;
			}
		}
	}

	public boolean makeIterationForOne() {
		if(!checkConfiguration()){
			return false;
		}
		Agent agent;
		State goalState;

		gui.setCanEdit(false);

		// assume everything is OK
		blocked = false;

		while (true) {
			if (isFinished()) {
				return true;
			}
			currentAgentID = (currentAgentID + 1) % agents.size();
			agent = agents.get(currentAgentID);
			goalState = agent.getGoalState();
			if (agent.getCurrentState() != goalState) {
				break;
			} else if (!finished.containsKey(agent)) {
				finished.put(agent, true);
			}
		}
		try {
			agent.makeMove();
		} catch (PathNotFoundException e) {
			SystemManager.getInstance().showErrorMsg(e.getMessage());
			blocked = true;
			gui.stopIterationThread();

			return false;
		}
		iterations++;

		System.out.println("Iteracja " + iterations);
		updateAgentStatistics(agent);
		goalState = agent.getGoalState();
		if (agent.getCurrentState() == goalState) {
			finished.put(agent, true);
		}
		return true;
	}

	public void agentGoalStateChanged(Agent agent){
		if(finished.containsKey(agent)){
			finished.remove(agent);
		}
	}

	private boolean checkConfiguration(){
		if(agents.size()==0){
			SystemManager.getInstance().showErrorMsg("Nie mozna wykonac symulacji. Brak agentow. Sprawdz konfiguracje, a nastepnie uruchom symulacje raz jeszcze.");
			return false;
		}
		return true;
	}

	public boolean isFinished() {
		if (finished.size() == agents.size()) {
			System.out.println("Koniec po " + iterations + " iteracji");
			int diff = 0;
			for (Agent agent : agents) {
				if(agentPathSize.get(agent)!=null && agentBestPathSize.get(agent)!=null){
					diff += agentPathSize.get(agent) - agentBestPathSize.get(agent);
				}
			}
			gui.showInformationMsg("Zostalo wykonanych " + diff + " dodatkowych iteracji.");
			return true;
		}
		return false;
	}

	/**
	 * can't calculate path for at least one agent, for stopping thread if it is running
	 * @return
	 */
	public boolean isBlocked() {
		return blocked;
	}

	private void calculateBestPaths() {
		for (Agent agent : agents) {
			Path agentPath = agent.getBestPath();
			if (agentPath == null) {
				showErrorMsg("Nie istnieje sciezka dla agenta");
				System.err.println("------ NIE ISTNIEJE SCIEZKA DLA AGENTA "
						+ agent.getID() + " ------");
			} else {
				System.out.println("agent " + agent.getID());
				agentBestPath.put(agent, agentPath);
				agentBestPathSize.put(agent, agentPath.size());
			}
		}
	}

	private void updateAgentStatistics(Agent agent){
		if(agentPathSize.containsKey(agent)){
			int size = agentPathSize.get(agent)+1;
			agentPathSize.put(agent, size);
			Path path = agentPath.get(agent);
			path.add(agent.getCurrentState());
			agentPath.put(agent, path);
		} else {
			agentPathSize.put(agent, 1);
			Path path = new Path();
			path.add(agent.getCurrentState());
			agentPath.put(agent, path);
		}
	}

	public int getCurrentAgentID() {
		return currentAgentID;
	}

	public Map<Agent, State> getAgentsFutureStates(int iteration){
		HashMap<Agent, State> map = new HashMap<Agent, State>();
		for (Agent agent : agents) {
			State s = agent.getFutureState(iteration);
			map.put(agent, s);
		}
		return map;
	}

	/**
	 * 
	 * @param iteration
	 * @param state
	 * @return number of agents wanted to enter to state in iteration moment
	 */
	public int getStatesAvailability(int iteration, State state){
		int number =0;
		for (Agent agent : agents) {
			State s = agent.getFutureState(iteration);
			if(state.equals(s)){
				number++;
			}
		}
		return number;
	}	

	public void showErrorMsg(String msg){
		gui.showErrorMsg(msg);
	}
}